{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Controlled-Z Gate\n",
    "\n",
    "*Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Outline\n",
    "In this tutorial, we will implement a controlled-Z gate using Quanlse Cloud Service. The outline of this tutorial is as follows:\n",
    "- Introduction\n",
    "- Preparation\n",
    "- Construct Hamiltonian\n",
    "- Generate and optimize pulse via Quanlse Cloud Service\n",
    "- Summary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "\n",
    "Theoretically, the controlled-Z gate (CZ gate) is a two-qubit quantum gate that adds a phase of $e^{i\\pi}$ to the target qubit if the control qubit and target qubit's state is $|11\\rangle$. The physical implementation of the CZ gate is realized using magnetic flux to tune the qubit's eigenfrequency - this is accomplished by slowly increasing the magnetic flux, waiting for some time to add a phase $\\pi$ and then decreasing the flux back to 0 \\[1\\]. CZ gate allows for faster two-qubit control than the cross-resonance gate due to stronger inductive coupling. \n",
    "\n",
    "The matrix representation of $U_{\\rm CZ}$ is:\n",
    "$$\n",
    "U_{\\rm CZ} = |0\\rangle\\langle 0| \\otimes I + |1\\rangle\\langle1| \\otimes \\hat{\\sigma}^z = \\begin{bmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 0 & 0 & -1 \\end{bmatrix}. \n",
    "$$  \n",
    "\n",
    "Using the circuit identity, the CNOT gate can be implemented by one CZ gate and two Hadamard gates since $H\\hat{\\sigma}^zH=\\hat{\\sigma}^x$ \\[1\\]. \n",
    "\n",
    "![cnot](figures/cz-cnot.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preparation\n",
    "After you have successfully installed Quanlse, you could run the Quanlse program below following this tutorial. To run this particular tutorial, you would need to import the following packages from Quanlse and other commonly-used Python libraries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import Hamiltonian-related modules \n",
    "from Quanlse.QHamiltonian import QHamiltonian as QHam\n",
    "from Quanlse.QOperator import duff, number\n",
    "\n",
    "# Import the optimizer for controlled-z gate\n",
    "from Quanlse.remoteOptimizer import remoteOptimizeCz\n",
    "\n",
    "# Import tools for result analysis\n",
    "from Quanlse.Utils.Functions import project\n",
    "\n",
    "# Import numpy and math\n",
    "from numpy import round\n",
    "from math import pi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Construct  Hamiltonian\n",
    "\n",
    "In our modeling, we will account for energy leakage for a two-qubit system by including the third energy level. The system Hamiltonian we will define is:\n",
    "\n",
    "$$\n",
    "\\hat{H}_{\\rm sys}(t) = (\\omega_{\\rm q0}-\\omega_{\\rm d0})\\hat{a}_0^\\dagger \\hat{a}_0+(\\omega_{\\rm q1}-\\omega_{\\rm d0}) \\hat{a}_1^\\dagger \\hat{a}_1+\\frac{\\alpha_0}{2}\\hat{a}_0^{\\dagger}\\hat{a}_0^\\dagger\\hat{a}_0\\hat{a}_0 + \\frac{\\alpha_1}{2}\\hat{a}_1^\\dagger\\hat{a}_1^\\dagger\\hat{a}_1\\hat{a}_1 + \\frac{g}{2}(\\hat{a}_0\\hat{a}_1^\\dagger+\\hat{a}_0^\\dagger\\hat{a}_1) + \\frac{A_0^z(t)}{2}\\hat{a}_0^\\dagger \\hat{a}_0,\n",
    "$$\n",
    "\n",
    "where $\\hat{a}_i^\\dagger$, $\\hat{a}_i$ are the creation and annihilation operators for the qubit $q_i$ ($i$=0, 1). The information regarding the hardware structure is specified by parameters: qubit frequency $\\omega_{qi}$, drive frequency $\\omega_{{\\rm d}i}$, anharmonicity $\\alpha_i$, and coupling strength $g$. $A_0^z(t)$ is the magnetic flux applying on qubit $q_0$.\n",
    "\n",
    "In Quanlse, we will construct the system Hamiltonian by adding up the three terms:\n",
    "$$\n",
    "\\hat{H}_{\\rm sys}(t) = \\hat{H}_{\\rm drift} + \\hat{H}_{\\rm coup} + \\hat{H}_{\\rm ctrl}(t). \n",
    "$$\n",
    "\n",
    "We will first define the necessary function arguments, including the sampling period, number of qubits, and the system's energy levels to consider. We then use the function ` QHamiltonian()` to initialize the Hamiltonian dictionary by passing in the variables we just defined."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Sampling period\n",
    "dt = 1.0\n",
    "\n",
    "# Number of qubits\n",
    "qubits = 2\n",
    "\n",
    "# System energy levels\n",
    "level = 3\n",
    "\n",
    "# Initilize the Hamiltonian\n",
    "ham = QHam(subSysNum=qubits, sysLevel=level, dt=dt)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After initializing the Hamiltonian dictionary, we will start to add terms to the Hamiltonian. Here, we define the parameters of the hardware that would be used later:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Specify the parameters of the hardware\n",
    "qubitArgs = {\n",
    "    \"coupling\": 0.0277 * (2 * pi),  # Coupling of Q0 and Q1\n",
    "    \"qubit_freq0\": 5.805 * (2 * pi),  # Frequency of Q0\n",
    "    \"qubit_freq1\": 5.205 * (2 * pi),  # Frequency of Q1\n",
    "    \"drive_freq0\": 5.205 * (2 * pi),  # Drive frequency on Q0 (rotating frame)\n",
    "    \"drive_freq1\": 5.205 * (2 * pi),  # Drive frequency on Q1 (rotating frame)\n",
    "    \"qubit_anharm0\": -0.217 * (2 * pi),  # Anharmonicity of Q0\n",
    "    \"qubit_anharm1\": -0.226 * (2 * pi)  # Anharmonicity of Q1\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we add the drift terms to the initalized Hamiltonian. The drift Hamiltonian $\\hat{H}_{\\rm drift}$ takes the form:\n",
    "$$\n",
    "\\hat{H}_{\\rm drift} = (\\omega_{\\rm q0}-\\omega_{\\rm d0})\\hat{n}_0+(\\omega_{\\rm q1}-\\omega_{\\rm d0})\\hat{n}_1+\\frac{\\alpha_0}{2}\\hat{a}_0^\\dagger\\hat{a}_0^\\dagger\\hat{a}_0\\hat{a}_0+\\frac{\\alpha_1}{2}\\hat{a}_1^\\dagger\\hat{a}_1^\\dagger\\hat{a}_1\\hat{a}_1 .\n",
    "$$\n",
    "Here, $\\hat{n}_i=\\hat{a}^\\dagger_i \\hat{a}_i$ is the number operator for qubit $q_i$. In Quanlse, this can be done using the function `addDrift()` which takes a Hamiltonian dictionary, a user-defined name, the list of qubit(s) the term acts upon, the according operators, and the amplitude:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add drift term(s)\n",
    "for qu in range(2):\n",
    "    # Add detuning term(s)\n",
    "    ham.addDrift(number, qu, (qubitArgs[f\"qubit_freq{qu}\"] - qubitArgs[f\"drive_freq{qu}\"]))\n",
    "    # Add anharmonicity term(s)\n",
    "    ham.addDrift(duff, qu, qubitArgs[f\"qubit_anharm{qu}\"] / 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we add the coupling Hamiltonian $\\hat{H}_{\\rm coup}$, which takes the form:\n",
    "$$\n",
    "\\hat{H}_{\\rm coup} = \\frac{g}{2}(\\hat{a}_0\\hat{a}_1^\\dagger+\\hat{a}_0^\\dagger\\hat{a}_1). \n",
    "$$\n",
    "\n",
    "The coupling Hamiltonian is added by the function `addCoupling()`. The function arguments include a Hamiltonian dictionary, a user-defined name, a list of qubit indices which the term acts upon, and the coupling strength. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add coupling term\n",
    "ham.addCoupling([0, 1], qubitArgs[\"coupling\"] / 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Noted that the optimizer in Quanlse will automatically add the control term:\n",
    "\n",
    "$$ \n",
    "\\hat{H}_{\\rm ctrl}(t) = \\frac{A_0^z(t)}{2}\\hat{a}_0^\\dagger \\hat{a}_0, \n",
    "$$\n",
    "\n",
    "thus we don't need to add this term manually."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With the system Hamiltonian built, we can now move on to the optimization."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generate and optimize pulse via Quanlse Cloud Service"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The optimization process usually takes a long time to process on local devices, however, we provide a cloud service that could speed up this process significantly. To use the Quanlse Cloud Service, the users need to acquire a token from http://quantum-hub.baidu.com."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import tools to get access to cloud service\n",
    "from Quanlse import Define\n",
    "\n",
    "# To use remoteOptimizerCz() on cloud, paste your token (a string) here\n",
    "Define.hubToken = ''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For this example, we can use `remoteOptimizeCz()` to generate and optimize the control pulses at the designated `targetInfidelity`. To use this function, the user needs to specify a Hamiltonian dictionary, the amplitude's bound, gate time, maximum iteration number, and the target infidelity. It returns a Hamiltonian and the infidelity found at the local minimum. `aBound` sets the bound for the strength of our pulse - a larger bound will lead to a larger search space. Hence, we can increase the number of iterations to reach a better result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "aBound=(-5, -1)  # The bound of the pulse's strength \n",
    "gateJob, infidelity = remoteOptimizeCz(ham, aBound=aBound, tg=40, maxIter=5, targetInfidelity=0.005)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We now extract the optimized pulse and the infidelity. The gate infidelity for performance assessment throughout this tutorial is defined as ${\\rm infid} = 1 - \\frac{1}{d}\\left|{\\rm Tr}[U^\\dagger_{\\rm goal}P(U)]\\right|$, where $U_{\\rm goal}=U_{\\rm CZ}$, $d$ is the dimension of $U_{\\rm goal}$ and $U$ is the unitary evolution of the system defined previously. The projected evolution $P(U)$ in particular describes the evolution projected to the computational subspace."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"minimum infidelity: {infidelity}\")\n",
    "gateJob.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The system we defined previously is constituted by two three-level qubits. This indicates that the time evolution operator for this system is a $9\\times 9$ matrix. To simulate the evolution, you can use `simulate()`, The projected evolution matrix $P(U)$ can be obtained by `project()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = ham.simulate(job=gateJob)\n",
    "process2d = project(result.result[0][\"unitary\"], qubits, level, 2)\n",
    "print(\"The projected evolution P(U):\\n\", round(process2d, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "This tutorial introduces controlled-Z gate's implementation using Quanlse Cloud Service. The users could follow this link [tutorial-cz-gate.ipynb](https://github.com/baidu/Quanlse/blob/main/Tutorial/EN/tutorial-cz.ipynb) to the GitHub page of this Jupyter Notebook document and run this program for themselves. The users are encouraged to try parameter values different from this tutorial to obtain the optimal result."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## References\n",
    "\\[1\\] [Krantz, Philip, et al. \"A quantum engineer's guide to superconducting qubits.\" *Applied Physics Reviews* 6.2 (2019): 021318.](https://doi.org/10.1063/1.5089550)"
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "6ad73c40f4f7e139e51b4243fd18c81784a68c09ce49361ba281b37d203f3e8e"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
